module Main where
import Control.Monad.Random
import System.IO.Unsafe
import System.Environment

data Attack = Stab | Normal | Smash deriving (Show)

data Stat = Strength Int Int Int
          | Stamina Int Int Int
          | Focus Int Int Int
          | Wait Int
          deriving (Show, Eq)

data StatType = Strength' | Stamina' | Focus' | Wait' deriving (Show, Eq)

data Unit = Fighter Stat Stat Stat Stat Attack deriving (Show)

strength :: Unit -> Int
strength (Fighter (Strength _ n _) _ _ _ _) = n

stamina :: Unit -> Int
stamina (Fighter _ (Stamina _ n _) _ _ _) = n

focus :: Unit -> Int
focus (Fighter _ _ (Focus _ n _) _ _) = n

wait :: Unit -> Int
wait (Fighter _ _ _ (Wait n) _) = n

attackType :: Unit -> Attack
attackType (Fighter _ _ _ _ x) = x

strHealTime :: Int
strHealTime = 100

staHealTime :: Int
staHealTime = 50

focHealTime :: Int
focHealTime = 20

regen :: Unit -> Unit
regen (Fighter str sta foc w a) = Fighter (regenStrength str) (regenStamina sta) (regenFocus foc) w a

regenStrength :: Stat -> Stat
regenStrength original@(Strength basestr str strheal)
    | basestr == str            = original
    | strheal >= strHealTime    = Strength basestr (str + 1) 0
    | otherwise                 = Strength basestr str (strheal + 1)

regenStamina :: Stat -> Stat
regenStamina original@(Stamina basesta sta staheal)
    | basesta == sta            = original
    | staheal >= staHealTime    = Stamina basesta (sta + 1) 0
    | otherwise                 = Stamina basesta sta (staheal + 1)

regenFocus :: Stat -> Stat
regenFocus original@(Focus basefoc foc focheal)
    | basefoc == foc            = original
    | focheal >= focHealTime    = Focus basefoc (foc + 1) 0
    | otherwise                 = Focus basefoc foc (focheal + 1)

buffStat :: StatType -> Int -> Unit -> Unit
buffStat stat n (Fighter str@(Strength basestr str' strheal) sta@(Stamina basesta sta' staheal) foc@(Focus basefoc foc' focheal) wait@(Wait wait') style)
    | stat == Strength' = normalizeStats $ Fighter (Strength basestr (str' + n) strheal) sta foc wait style
    | stat == Stamina' = normalizeStats $ Fighter str (Stamina basesta (sta' + n) staheal) foc wait style
    | stat == Focus' =  normalizeStats $ Fighter str sta (Focus basefoc (foc' + n) focheal) wait style
    | stat == Wait' = normalizeStats $ Fighter str sta foc (Wait (wait' + n)) style

debuffStat :: StatType -> Int -> Unit -> Unit
debuffStat s n u = buffStat s (-1 * n) u

buffStrength' :: Int -> Unit -> Unit
buffStrength' n u = buffStat Strength' n u

buffStamina' :: Int -> Unit -> Unit
buffStamina' n u = buffStat Stamina' n u

debuffStrength' :: Int -> Unit -> Unit
debuffStrength' n u = debuffStat Strength' n u

debuffStamina' :: Int -> Unit -> Unit
debuffStamina' n u = debuffStat Stamina' n u

increaseWait :: Int -> Unit -> Unit
increaseWait n u = buffStat Wait' n u

decreaseWait :: Int -> Unit -> Unit
decreaseWait n u = debuffStat Wait' n u

normalizeStats :: Unit -> Unit
normalizeStats = normalizeStrength' . normalizeStamina' . normalizeFocus' . normalizeWait

normalizeStrength' :: Unit -> Unit
normalizeStrength' unit@(Fighter (Strength basestr str strheal) sta foc wait style)
    | str < 0 = (Fighter (Strength basestr 0 strheal) sta foc wait style)
    | otherwise = unit

normalizeStamina' :: Unit -> Unit
normalizeStamina' unit@(Fighter str (Stamina basesta sta staheal) foc wait style)
    | sta < 0 = (Fighter str (Stamina basesta 0 staheal) foc wait style)
    | otherwise = unit

normalizeFocus' :: Unit -> Unit
normalizeFocus' unit@(Fighter str sta (Focus basefoc foc focheal) wait style)
    | foc < 0 = (Fighter str sta (Focus basefoc 0 focheal) wait style)
    | otherwise = unit

normalizeWait :: Unit -> Unit
normalizeWait unit@(Fighter str sta foc (Wait wait) style)
    | wait < 0 = (Fighter str sta foc (Wait 0) style)
    | otherwise = unit

dead :: Unit -> Bool
dead unit
    | strength unit <= 0 = True
    | otherwise = False

alive :: Unit -> Bool
alive = not . dead

canAct :: Unit -> Bool
canAct unit
    | wait unit == 0 && stamina unit > 0 = True
    | otherwise = False

die :: (RandomGen g) => Int -> Rand g Int
die n = getRandomR (1, n)

roll :: Int -> Int
roll n = unsafePerformIO $ evalRandIO $ die n

hit :: Int -> Int -> Bool
hit x y = (x + (roll 6)) > (y + (roll 6))

dmg :: Int -> Int -> Int
dmg x y = (x + (roll 6)) - (y + (roll 6))

pierce :: Int -> Int -> Bool
pierce x y = (x + (roll 6)) > (y + (roll 6) + pierceRequirement)

pierceRequirement :: Int
pierceRequirement = 3

attack :: Unit -> Unit -> (Unit, Unit)
attack attacker target
    | stamina target > 0 &&
        hit (stamina attacker) (stamina target) = (swung, debuffStamina' damage target)
    | stamina target > 0                        = (swung, target)
    | stamina target <= 0 ||
        pierced                                 = (swung, debuffStrength' 1 target)
    where damage = dmg (strength attacker) (strength target)
          swung = increaseWait 10 attacker
          pierced = pierce (focus attacker) (focus target)

swap :: (a, b) -> (b, a)
swap (a, b) = (b, a)

turn :: Unit -> Unit -> (Unit, Unit)
turn alex jamie
    | canAct alex && canAct jamie   = let attacked = attack alex jamie
                                          attacker = fst attacked
                                          target = snd attacked in
                                            if alive target && canAct target
                                            then swap $ attack target attacker
                                            else attacked
    | canAct alex                   = attack alex waited_jamie
    | canAct jamie                  = swap $ attack jamie waited_alex
    | otherwise                     = (waited_alex, waited_jamie)
    where waited_alex = decreaseWait 1 alex
          waited_jamie = decreaseWait 1 jamie

fight2death :: Unit -> Unit -> Float
fight2death alex jamie
    | dead new_alex     = 0.0
    | dead new_jamie    = 1.0
    | otherwise         = fight2death (regen new_alex) (regen new_jamie)
    where fought = turn alex jamie
          new_alex = fst fought
          new_jamie = snd fought

al = Fighter (Strength 2 2 0) (Stamina 1 1 0) (Focus 1 1 0) (Wait 0) Normal
jame = Fighter (Strength 1 1 0) (Stamina 1 1 0) (Focus 1 1 0) (Wait 0) Normal

main :: IO ()
main = do x <- getArgs
          let n = head x
          print $ (sum [ fight2death al jame | _ <- [1..read n] ]) / (read n :: Float)